﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Data.Linq.SqlClient;
using System.IO;
using System.Linq;
using System.Linq.Dynamic;
using System.Linq.Expressions;
using System.Reflection;
using System.Runtime.Serialization.Json;
using System.Text;
using Comissoes.Utilities;
using System.ComponentModel;

namespace Comissoes.JQGridObject
{
    public class JQGridRow
    {
        public int? id;
        public List<string> cell = new List<string>();
    }

    public class JQGridObject
    {
        public int total;
        public int page;
        public int records;
        public List<JQGridRow> rows = new List<JQGridRow>();
    }
    
    public class JQGridBuilder
    {
        private IQueryable query { get; set; }
        private String order { get; set; }
        private String searchField { get; set; }
        private String searchString { get; set; }
        private String searchKey { get; set; }
        private String searchOper { get; set; }
        private Boolean isSearch { get; set; }
        private Int32 pageIndex { get; set; }
        private Int32 page { get; set; }
        private Int32 pageSize { get; set; }
        private Int32 totalRecords { get; set; }
        private IDictionary<string, string> formats { get; set; }       

        /// <summary>
        /// Cria o objeto para a estrutura do grid
        /// </summary>
        /// <param name="query"></param>
        /// <param name="page"></param>
        /// <param name="sidx"></param>
        /// <param name="sord"></param>
        /// <param name="searchField"></param>
        /// <param name="searchString"></param>
        /// <param name="searchOper"></param>
        /// <param name="searchKey"></param>
        /// <param name="isSearch"></param>
        /// <param name="pageSize"></param>
        public JQGridBuilder(IQueryable query, Int32 page, String sidx, String sord, String searchField, String searchString, String searchOper, String searchKey, Boolean isSearch, Int32 pageSize)
        {
            this.query = query;
            this.page = page;
            this.order = String.Format("{0} {1}", sidx, sord);
            this.searchField = searchField;
            this.searchString = searchString;
            this.searchOper = searchOper;
            this.searchKey = searchKey;
            this.isSearch = isSearch;
            this.pageIndex = page - 1;
            this.pageSize = pageSize;
            this.totalRecords = query.Count();
        }
        
        /// <summary>
        /// Cria o objeto para a estrutura do grid
        /// </summary>
        /// <param name="query">Query Linq</param>
        /// <param name="page"></param>
        /// <param name="sidx"></param>
        /// <param name="sord"></param>
        /// <param name="searchField"></param>
        /// <param name="searchString"></param>
        /// <param name="searchOper"></param>
        /// <param name="searchKey"></param>
        /// <param name="isSearch"></param>
        /// <param name="pageSize"></param>
        /// <param name="formats">Dicionário de strings para formatação: A - Moeda
        ///                                                              B - Data
        ///                                                              C - Documento
        ///                                                              D - Telefone</param>
        public JQGridBuilder(IQueryable query, Int32 page, String sidx, String sord, String searchField, String searchString, String searchOper, String searchKey, Boolean isSearch, Int32 pageSize, IDictionary<string, string> formats)
        {
                this.query = query;
                this.page = page;
                this.order = String.Format("{0} {1}", sidx, sord);
                this.searchField = searchField;
                this.searchString = searchString;
                this.searchOper = searchOper;
                this.searchKey = searchKey;
                this.isSearch = isSearch;
                this.pageIndex = page - 1;
                this.pageSize = pageSize;
                this.totalRecords = query.Count();
                this.formats = formats;
        }

        /// <summary>
        /// Retorna o grid montado
        /// </summary>
        /// <returns></returns>
        public JQGridObject GetObject()
        {
            if (isSearch)
            {
                if (!string.IsNullOrEmpty(searchString))
                {
                    switch (searchOper)
                    {
                        case "eq":
                            try
                            {
                                this.query = this.query.Where(this.searchField + ".Equals(@0)", Int32.Parse(searchString));
                            }
                            catch
                            {
                                this.query = this.query.Where(this.searchField + ".Equals(@0)", searchString);
                            }
                            break;
                        case "cn":
                            this.query = this.query.Where(this.searchField + ".Contains(@0)", searchString);
                            break;
                    }
                }
            }
            try
            {
                var totalPages = (int)Math.Ceiling(((float)totalRecords) / (float)pageSize);

                this.query = this.query.OrderBy(this.order).Skip(this.pageIndex * this.pageSize).Take(this.pageSize);

                JQGridObject JQGridObject = new JQGridObject();
                JQGridObject.total = totalPages;
                JQGridObject.page = page;
                JQGridObject.records = this.totalRecords;
                foreach (var r in this.query)
                {
                    JQGridRow row = new JQGridRow()
                    {
                        id = int.Parse(r.GetType().GetProperty(this.searchKey).GetValue(r, null).ToString()),
                        cell = ExtensionMethods.GetPropertyList(r, this.formats)
                    };
                    JQGridObject.rows.Add(row);
                }
                return JQGridObject;
            }
            catch(Exception ex)
            {
                if (ex.InnerException != null)
                {
                    String message = ex.InnerException.Message;
                }

                return null;
            }
        }
    }

    public class JsonSerializer
    {
        public static string ToJsonObject(object obj)
        {
            DataContractJsonSerializer serializer = new DataContractJsonSerializer(obj.GetType());
            using (MemoryStream ms = new MemoryStream())
            {
                serializer.WriteObject(ms, obj);
                StringBuilder sb = new StringBuilder();
                sb.Append(Encoding.Default.GetString(ms.ToArray()));
                return sb.ToString();
            }
        }
    }

    /// <summary>
    /// Classe para ordenar as colunas do grid
    /// </summary>
    public class DeclarationOrderComparator : IComparer
    {
        int IComparer.Compare(Object x, Object y)
        {
            PropertyInfo first = x as PropertyInfo;
            PropertyInfo second = y as PropertyInfo;

            if (first.MetadataToken < second.MetadataToken)
                return -1;
            else if (first.MetadataToken > second.MetadataToken)
                return 1;

            return 0;
        }
    }

    public static class ExtensionMethods
    {
        public static Boolean Existe(this Int32? valor)
        {
            return valor.HasValue && valor != 0;
        }

        public static IQueryable<T> OrderBy<T>(this IQueryable<T> source, string propertyName, bool asc)
        {
            var type = typeof(T);
            string methodName = asc ? "OrderBy" : "OrderByDescending";
            var property = type.GetProperty(propertyName);
            var parameter = Expression.Parameter(type, "p");
            var propertyAccess = Expression.MakeMemberAccess(parameter, property);
            var orderByExp = Expression.Lambda(propertyAccess, parameter);
            MethodCallExpression resultExp = Expression.Call(typeof(Queryable), methodName,
                              new Type[] { type, property.PropertyType },
                              source.Expression, Expression.Quote(orderByExp));
            return source.Provider.CreateQuery<T>(resultExp);
        }

        public static IQueryable<T> Like<T>(this IQueryable<T> source, string propertyName, string keyword)
        {
            var type = typeof(T);
            var property = type.GetProperty(propertyName);
            var parameter = Expression.Parameter(type, "p");
            var propertyAccess = Expression.MakeMemberAccess(parameter, property);
            var constant = Expression.Constant("%" + keyword + "%");
            var like = typeof(SqlMethods).GetMethod("Like", new Type[] { typeof(string), typeof(string) });
            MethodCallExpression methodExp = Expression.Call(null, like, propertyAccess, constant);
            Expression<Func<T, bool>> lambda = Expression.Lambda<Func<T, bool>>(methodExp, parameter);
            
            return source.Where(lambda);
        }

        public static List<string> GetPropertyList(object obj)
        {
            return GetPropertyList(obj, null);
        }
        
        public static List<string> GetPropertyList(object obj, IDictionary<string, string> formats)
        {
            List<string> propertyList = new List<string>();

            Type type = obj.GetType();

            PropertyInfo[] properties = type.GetProperties(BindingFlags.Instance |
                                                            BindingFlags.Public);

            //Aplica a ordenação das colunas do grid
            Array.Sort(properties, new DeclarationOrderComparator());

            bool ok = false;
            foreach (PropertyInfo property in properties)
            {
                object o = property.GetValue(obj, null);

                if (o != null)
                {
                    try
                    {
                        if (formats != null)
                        {
                            foreach (var f in formats)
                            {
                                if (property.Name.Equals(f.Key))
                                {
                                    MaskedTextProvider mask = new MaskedTextProvider("0");
                                    String value = String.Empty;
                                    String[] prm = new String[] { };

                                    switch (f.Value)
                                    {
                                        case "A":
                                            value = string.Format("{0:C}", o);
                                            break;
                                        case "B":
                                            value = string.Format("{0:dd/MM/yyyy}", o);
                                            break;
                                        case "C":
                                            prm = o.ToString().Split('|');
                                            switch (prm[1])
                                            {
                                                case "CPF":
                                                    mask = new MaskedTextProvider(@"000\.000\.000-00");
                                                    break;
                                                case "CNPJ":
                                                    mask = new MaskedTextProvider(@"00\.000\.000/0000-00");
                                                    break;
                                                case "IE":
                                                    switch (prm[2])
                                                    {
                                                        case "AC": mask = new MaskedTextProvider(@"00\.000\.000/000-00"); break;
                                                        case "AL": mask = new MaskedTextProvider("000000000"); break;
                                                        case "AM": mask = new MaskedTextProvider(@"00\.000\.000-0"); break;
                                                        case "AP": mask = new MaskedTextProvider("000000000"); break;
                                                        case "BA": mask = new MaskedTextProvider(@"000\.000\.00-0"); break;
                                                        case "CE": mask = new MaskedTextProvider("00000000-0"); break;
                                                        case "DF": mask = new MaskedTextProvider("00000000000-00"); break;
                                                        case "ES": mask = new MaskedTextProvider(@"000\.000\.00-0"); break;
                                                        case "GO": mask = new MaskedTextProvider(@"00\.000\.000-0"); break;
                                                        case "MA": mask = new MaskedTextProvider("000000000"); break;
                                                        case "MG": mask = new MaskedTextProvider(@"000\.000\.000/0000"); break;
                                                        case "MS": mask = new MaskedTextProvider("000000000"); break;
                                                        case "MT": mask = new MaskedTextProvider("000000000"); break;
                                                        case "PA": mask = new MaskedTextProvider("00-000000-0"); break;
                                                        case "PB": mask = new MaskedTextProvider("00000000-0"); break;
                                                        case "PE": mask = new MaskedTextProvider(@"00\.0\.000\.0000000-0"); break;
                                                        case "PI": mask = new MaskedTextProvider("000000000"); break;
                                                        case "PR": mask = new MaskedTextProvider("00000000-00"); break;
                                                        case "RJ": mask = new MaskedTextProvider(@"00\.000\.00-0"); break;
                                                        case "RN": mask = new MaskedTextProvider(@"00\.000\.000-0"); break;
                                                        case "RO": mask = new MaskedTextProvider(@"000\.00000-0"); break;
                                                        case "RR": mask = new MaskedTextProvider("00000000-0"); break;
                                                        case "RS": mask = new MaskedTextProvider("000-0000000"); break;
                                                        case "SC": mask = new MaskedTextProvider(@"000\.000\.000"); break;
                                                        case "SE": mask = new MaskedTextProvider("000000000-0"); break;
                                                        case "SP": mask = new MaskedTextProvider(@"000\.000\.000\.000"); break;
                                                        case "TO": mask = new MaskedTextProvider("00000000000"); break;
                                                    }
                                                    break;
                                            }
                                            break;
                                        case "D":
                                            mask = new MaskedTextProvider("(00) 0000-0000");
                                            break;
                                    }

                                    if (o.ToString().Contains("|"))
                                    {
                                        if (mask.Set(prm[0]))
                                        {
                                            value = mask.ToString();
                                        }
                                    }
                                    if (mask.Set(o.ToString()))
                                    {
                                        value = mask.ToString();
                                    }

                                    propertyList.Add(value);
                                    ok = true;
                                    break;
                                }
                            }
                        }
                        if (!ok)
                            propertyList.Add(o.ToString());

                        ok = false;
                    }
                    catch
                    {
                        propertyList.Add(string.Empty);
                    }
                }

                else
                    propertyList.Add(string.Empty);
            }
            return propertyList;
        }
    }
}